package ArithmeticLibrary.Util;

import java.math.BigInteger;

/**
 * A class that receives a prime number 'p' in the form of BigInteger 
 * and supports mathematical operations on BigInteger numbers modulo 'p'
 * 
 * @author Lea golan
 *
 */
public class BigIntegerModulo {

	private static final long serialVersionUID = 1L;
	private java.math.BigInteger p;//the prime number that will be used for modulo calculations

	/**
	 * constructs a new instance according to the parameters
	 * 
	 * @param p - a prime number
	 */
	public BigIntegerModulo(java.math.BigInteger p) {
		this.p = p;
	}
	
	/**
	 * compares this to obj
	 */
	public boolean equals(java.lang.Object obj) {
		if (obj == this){
            return true;
        }
        if (!(obj instanceof BigIntegerModulo)){
            return false;
        }
        
        BigIntegerModulo other = (BigIntegerModulo) obj;
        // equals of BigInteger returns true iff the numeric values of both objects are identical
        return this.p.equals(other.p);
	}
	
	/**
	 * performs addition on two operands
	 * 
	 * @param val1 - first operand
	 * @param val2 - second operand
	 * @return the result of adding both operands
	 */
	public java.math.BigInteger addModulo(java.math.BigInteger val1, java.math.BigInteger val2){
		return (val1.add(val2)).mod(p);
	}
    
	/**
	 * performs subtraction on two operands
	 * 
	 * @param val1 - first operand
	 * @param val2 - second operand
	 * @return the result of subtracting the first operand from the second
	 */
	public java.math.BigInteger subtractModulo(java.math.BigInteger val1, java.math.BigInteger val2){
		return val1.subtract(val2).mod(p);
	}
	
	/**
	 * performs multiplying on two operands
	 * 
	 * @param val1 - first operand
	 * @param val2 - second operand
	 * @return the result of multiplying both operands
	 */
	public java.math.BigInteger multiplyModulo(java.math.BigInteger val1, java.math.BigInteger val2) {
		return val1.multiply(val2).mod(p);
	}
	
	/**
	 * performs division on two operands
	 * 
	 * @param val1 - first operand
	 * @param val2 - second operand
	 * @return the result of dividing the first operand by the second
	 */
	public java.math.BigInteger divideModulo(java.math.BigInteger val1, java.math.BigInteger val2) 
		throws Exception{
		if (val2.signum() == 0) throw new Exception("divide by zero");
		BigInteger revVal2 = multInverseModulo(val2);
		BigInteger op = val1.multiply(revVal2);
		BigInteger temp = op.mod(p);
		return temp;
	}
	
	/**
	 * calculates power on a given operand
	 * 
	 * @param val - operand
	 * @param pow - exponent
	 * @return the result of val^pow
	 */
	public java.math.BigInteger powModulo(java.math.BigInteger val, java.math.BigInteger pow){
		return val.modPow(pow, p);
	}
	
	/************ unary functions and getters ************/
	
	/**
	 * calculates additive inverse modulo p
	 * 
	 * @param val - an operand
	 * @return -val mod p
	 */
	public java.math.BigInteger addInverseModulo(java.math.BigInteger val){
		return val.negate().mod(p);
	}
    
	/**
	 * calculates multiplicative inverse modulo p
	 * 
	 * @param val - an operand
	 * @return val^-1 mod p
	 */
	public java.math.BigInteger multInverseModulo(java.math.BigInteger val) throws Exception{
		if (val.signum() == 0) throw new Exception("BigInteger.Zero is in invertible");
		return val.modInverse(p);
	}
     
	/**
	 * given a number, calculates the number modulo p
	 * 
	 * @param val - an integer operand
	 * @return val mod p
	 */
	public java.math.BigInteger getValModulo(int val){
		return BigInteger.valueOf(val).mod(p);
	}
	
	/**
	 * given a number, calculates the number modulo p
	 * 
	 * @param newValue - a BigInteger operand
	 * @return newValue mod p
	 */
	public java.math.BigInteger getValModulo(java.math.BigInteger newValue) {
		return newValue.mod(p);
	}
	
	/**
	 * getter for p
	 * @return p
	 */
	public java.math.BigInteger getPModulo(){
		return this.p;
	}

}
